<?php

namespace PhpYes\Modules\Uri;


class HttpUri extends Uri
{
    const SchemeName = 'http';

    /**
     * @return string
     */
    public function getScheme()
    {
        return self::SchemeName;
    }

    /**
     * @return int
     */
    public function getDefaultPort()
    {
        return 80;
    }

    /**
     * @param string $uri
     * @return mixed
     * @throws \Exception
     */
    public function parse($uri)
    {
        $this->clearMemberVariables();

        if (empty($uri)) {
            throw new \Exception('error argument uri, uri should not be empty.');
        }

        // http-URI = "http:" "//" authority path-abempty [ "?" query ] [ "#" fragment ]
        // https-URI = "https:" "//" authority path-abempty [ "?" query ] [ "#" fragment ]

        // scheme

        // authority
        // The authority component is preceded by a double slash ("//") and is
        // terminated by the next slash ("/"), question mark ("?"), or number
        // sign ("#") character, or by the end of the URI.

        // path-abempty
        // The path is terminated by the first question mark ("?") or number sign ("#") character, or by the end of the URI.
        // If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character.
        // If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//").

        // query
        // The query component is indicated by the first question mark ("?") character
        // and terminated by a number sign ("#") character or by the end of the URI.

        $scheme = $this->getScheme();
        $ss = $scheme . '://';
        $len = strlen($ss);
        $uriLen = strlen($uri);
        if (strncasecmp($ss, $uri, $len) == 0) {
            if ($uriLen == $len) {
                throw new \Exception('error argument uri:' . $uri . ', uri is not valid.');
            }
            $lastPos = $len;
        } else {
            $lastPos = 0;
        }

        $component = self::MainComponentAuthority;
        for ($i = $lastPos; $i < $uriLen; $i++) {
            $c = $uri[$i];

            if ($component == self::MainComponentAuthority) {
                if ($c == '/') {
                    $component = self::MainComponentPath;
                    $this->path .= $c;
                } elseif ($c == '?') {
                    $component = self::MainComponentQuery;
                } elseif ($c == '#') {
                    if ($uriLen > $i + 1) {
                        $this->fragment = substr($uri, $i + 1);
                    }
                    break;
                } else {
                    $this->authority .= $c;
                }
            } elseif ($component == self::MainComponentPath) {
                if ($c == '?') {
                    $component = self::MainComponentQuery;
                } elseif ($c == '#') {
                    if ($uriLen > $i + 1) {
                        $this->fragment = substr($uri, $i + 1);
                    }
                    break;
                } else {
                    $this->path .= $c;
                }
            } elseif ($component == self::MainComponentQuery) {
                if ($c == '#') {
                    if ($uriLen > $i + 1) {
                        $this->fragment = substr($uri, $i + 1);
                    }
                    break;
                } else {
                    $this->query .= $c;
                }
            } elseif ($component == self::MainComponentFragment) {
                throw new \Exception('程序内部错误,程序逻辑不正确');
            } else {
                throw new \Exception('程序内部错误,程序逻辑不正确');
            }
        }

        $this->parseAuthority();

        if (empty($this->path)) {
            $this->path = '/';
        } else {
            if (!self::checkPathAbempty($this->path)) {
                throw new \Exception('error argument uri:' . $uri . ', path is not valid.');
            }
        }

        if ($this->query && !self::checkQuery($this->query)) {
            throw new \Exception('error argument uri:' . $uri . ', query is not valid.');
        }

        if ($this->fragment && !self::checkFragment($this->fragment)) {
            throw new \Exception('error argument uri:' . $uri . ', fragment is not valid.');
        }
    }

    /**
     * @param string $baseUri
     * @param string $relativeRef
     * @return mixed
     * @throws \Exception
     */
    public function parseRelativeRef($baseUri, $relativeRef)
    {
        if (empty($baseUri)) {
            throw new \Exception('Base uri must exist for parsing relative-ref');
        }

        $uri = $baseUri . $relativeRef;
        $this->parse($uri);
    }

    /**
     * @return mixed
     * @throws \Exception
     */
    public function normalize()
    {
        if ($this->normalizeUri) {
            return;
        }

        $this->normalizeUri = $this->getScheme() . ':';
        $authority = '';

        if ($this->userInfo) {
            $this->userInfo = self::normalizePctTriplet($this->userInfo);
            $authority .= $this->userInfo . '@';
        }
        if ($this->host) {
            $this->host = self::normalizeHost($this->hostType, $this->host);
            if ($this->hostType == self::HostTypeIPv6Address || $this->hostType == self::HostTypeIPvFuture) {
                $authority .= '[' . $this->host . ']';
            } else {
                $authority .= $this->host;
            }
        }
        if ($this->port && $this->port != $this->getDefaultPort()) {
            $authority .= ':' . $this->port;
        }
        if ($authority) {
            $this->normalizeUri .= '//' . $authority;
        }

        if ($this->path) {
            $this->path = self::removeDotSegments($this->path);
            $this->path = self::normalizePctTriplet($this->path);
            $this->normalizeUri .= $this->path;
        }
        if ($this->query) {
            $this->query = self::normalizePctTriplet($this->query);
            $this->normalizeUri .= '?' . $this->query;
        }
        if ($this->fragment) {
            $this->fragment = self::normalizePctTriplet($this->fragment);
            $this->normalizeUri .= '#' . $this->fragment;
        }
    }
}